package com.riversql.sql;

import java.beans.PropertyChangeListener;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.util.Calendar;
import java.util.Date;

import com.riversql.util.PropertyChangeReporter;
import com.riversql.util.StringManager;
import com.riversql.util.StringManagerFactory;
import com.riversql.util.StringUtilities;
//import com.riversql.util.log.ILogger;
//import com.riversql.util.log.LoggerController;

/**
 * This represents a connection to an SQL server. it is basically a wrapper
 * around <TT>java.sql.Connection</TT>.
 * 
 * @author <A HREF="mailto:colbell@users.sourceforge.net">Colin Bell</A>
 */
public class SQLConnection {
	private ISQLDriver _sqlDriver;

	public interface IPropertyNames {
		String AUTO_COMMIT = "autocommit";
		String CATALOG = "catalog";
	}

	/** Internationalized strings for this class. */
	private static final StringManager s_stringMgr = StringManagerFactory
			.getStringManager(SQLConnection.class);

	/** Logger for this class. */
	// private final static ILogger s_log =
	// LoggerController.createLogger(SQLConnection.class);

	/** The <TT>java.sql.Connection</TT> this object is wrapped around. */
	private Connection _conn;

	/** Connectiopn properties specified when connection was opened. */
	private final SQLDriverPropertyCollection _connProps;

	private boolean _autoCommitOnClose = false;

	private Date _timeOpened;
	private Date _timeClosed;

	/** Object to handle property change events. */
	private transient PropertyChangeReporter _propChgReporter;

	private SQLDatabaseMetaData metaData = null;

	public SQLConnection(Connection conn,
			SQLDriverPropertyCollection connProps, ISQLDriver sqlDriver) {
		super();
		_sqlDriver = sqlDriver;
		if (conn == null) {
			throw new IllegalArgumentException("SQLConnection == null");
		}
		_conn = conn;
		_connProps = connProps;
		_timeOpened = Calendar.getInstance().getTime();
		metaData = new SQLDatabaseMetaData(this);
	}

	public void close() throws SQLException {
		SQLException savedEx = null;
		if (_conn != null) {
			// s_log.debug("Closing connection");
			try {
				if (!_conn.getAutoCommit()) {
					if (_autoCommitOnClose) {
						_conn.commit();
					} else {
						_conn.rollback();
					}
				}
			} catch (SQLException ex) {
				savedEx = ex;
			}
			_conn.close();
			_conn = null;
			_timeClosed = Calendar.getInstance().getTime();
			if (savedEx != null) {
				// s_log.debug("Connection close failed", savedEx);
				throw savedEx;
			}
			// s_log.debug("Connection closed successfully");
		}
	}

	public void commit() throws SQLException {
		validateConnection();
		_conn.commit();
	}

	public void rollback() throws SQLException {
		validateConnection();
		_conn.rollback();
	}

	/**
	 * Retrieve the properties specified when connection was opened. This can be
	 * <TT>null</TT>.
	 * 
	 * @return Connection properties.
	 */
	public SQLDriverPropertyCollection getConnectionProperties() {
		return _connProps;
	}

	public boolean getAutoCommit() throws SQLException {
		validateConnection();
		return _conn.getAutoCommit();
	}

	public void setAutoCommit(boolean value) throws SQLException {
		validateConnection();
		final Connection conn = getConnection();
		final boolean oldValue = conn.getAutoCommit();
		if (oldValue != value) {
			_conn.setAutoCommit(value);
			getPropertyChangeReporter().firePropertyChange(
					IPropertyNames.AUTO_COMMIT, oldValue, value);
		}
	}

	public boolean getCommitOnClose() {
		return _autoCommitOnClose;
	}

	public int getTransactionIsolation() throws SQLException {
		validateConnection();
		return _conn.getTransactionIsolation();
	}

	public void setTransactionIsolation(int value) throws SQLException {
		validateConnection();
		_conn.setTransactionIsolation(value);
	}

	public void setCommitOnClose(boolean value) {
		_autoCommitOnClose = value;
	}

	public Statement createStatement() throws SQLException {
		validateConnection();
		return _conn.createStatement();
	}

	public PreparedStatement prepareStatement(String sql) throws SQLException {
		validateConnection();
		return _conn.prepareStatement(sql);
	}

	/**
	 * Retrieve the time that this connection was opened. Note that this time is
	 * the time that this <TT>SQLConnection</TT> was created, not the time that
	 * the <TT>java.sql.Connection</TT> object that it is wrapped around was
	 * opened.
	 * 
	 * @return Time connection opened.
	 */
	public Date getTimeOpened() {
		return _timeOpened;
	}

	/**
	 * Retrieve the time that this connection was closed. If this connection is
	 * still opened then <TT>null</TT> will be returned..
	 * 
	 * @return Time connection closed.
	 */
	public Date getTimeClosed() {
		return _timeClosed;
	}

	/**
	 * Retrieve the metadata for this connection.
	 * 
	 * @return The <TT>SQLMetaData</TT> object.
	 */
	public SQLDatabaseMetaData getSQLMetaData() {
		return metaData;
	}

	public Connection getConnection() {
		/*
		 * This is extremely useful when trying to track down Swing UI freezing.
		 * However, it currently fills the log which obscures other debug
		 * messages even though UI performance is acceptable, so it is commented
		 * out until it is needed later. if (s_log.isDebugEnabled()) { try { if
		 * (SwingUtilities.isEventDispatchThread() ) { throw new Exception(); }
		 * } catch (Exception e) { s_log.debug("GUI thread doing database work",
		 * e); } }
		 */
		return _conn;
	}

	public String getCatalog() throws SQLException {
		validateConnection();
		return getConnection().getCatalog();
	}

	public void setCatalog(String catalogName) throws SQLException {
		validateConnection();
		final Connection conn = getConnection();
		final String oldValue = conn.getCatalog();
		if (!StringUtilities.areStringsEqual(oldValue, catalogName)) {
			try {
				conn.setCatalog(catalogName);
			} catch (SQLException ex) {
				// MS SQL Server throws an exception if the catalog name
				// contains a period without it being quoted.
				conn.setCatalog(quote(catalogName));
			}
			getPropertyChangeReporter().firePropertyChange(
					IPropertyNames.CATALOG, oldValue, catalogName);
		}
	}

	public SQLWarning getWarnings() throws SQLException {
		validateConnection();
		return _conn.getWarnings();
	}

	/**
	 * Add a listener for property change events.
	 * 
	 * @param lis
	 *            The new listener.
	 */
	public void addPropertyChangeListener(PropertyChangeListener listener) {
		if (listener != null) {
			getPropertyChangeReporter().addPropertyChangeListener(listener);
		} else {
			// s_log.debug("Attempted to add a null PropertyChangeListener");
		}
	}

	/**
	 * Remove a property change listener.
	 * 
	 * @param lis
	 *            The listener to be removed.
	 */
	public void removePropertyChangeListener(PropertyChangeListener listener) {
		if (listener != null) {
			getPropertyChangeReporter().removePropertyChangeListener(listener);
		} else {
			// s_log.debug("Attempted to remove a null PropertyChangeListener");
		}
	}

	protected void validateConnection() throws SQLException {
		if (_conn == null) {
			throw new SQLException(
					s_stringMgr.getString("SQLConnection.noConn"));
		}
	}

	/**
	 * Retrieve the object that reports on property change events. If it doesn't
	 * exist then create it.
	 * 
	 * @return PropertyChangeReporter object.
	 */
	private synchronized PropertyChangeReporter getPropertyChangeReporter() {
		if (_propChgReporter == null) {
			_propChgReporter = new PropertyChangeReporter(this);
		}
		return _propChgReporter;
	}

	private String quote(String str) {
		String identifierQuoteString = "";
		try {
			identifierQuoteString = getSQLMetaData().getIdentifierQuoteString();
		} catch (SQLException ex) {
			// s_log.debug(
			// "DBMS doesn't supportDatabasemetaData.getIdentifierQuoteString",
			// ex);
		}
		if (identifierQuoteString != null && !identifierQuoteString.equals(" ")) {
			return identifierQuoteString + str + identifierQuoteString;
		}
		return str;
	}

	public ISQLDriver getSQLDriver() {
		return _sqlDriver;
	}

}
